{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 基于生成器的协程"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<generator object generator_func at 0x000002A99E2F9AF0>\n",
      "['__del__', '__iter__', '__name__', '__next__', '__qualname__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']\n"
     ]
    }
   ],
   "source": [
    "# 对比 generator 和 coroutine\n",
    "\n",
    "def generator_func():\n",
    "    yield\n",
    "\n",
    "gen = generator_func()\n",
    "\n",
    "print(gen)\n",
    "print(sorted(set(dir(gen)) - set(dir(object))))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<coroutine object coroutine_func at 0x000002A99E2FA3B0>\n",
      "['__await__', '__del__', '__name__', '__qualname__', 'close', 'cr_await', 'cr_code', 'cr_frame', 'cr_origin', 'cr_running', 'send', 'throw']\n"
     ]
    }
   ],
   "source": [
    "async def coroutine_func():\n",
    "    await coroutine_func()\n",
    "\n",
    "coro = coroutine_func()\n",
    "\n",
    "print(coro)\n",
    "print(sorted(set(dir(coro)) - set(dir(object))))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "ename": "SyntaxError",
     "evalue": "'yield' outside function (1574197157.py, line 7)",
     "output_type": "error",
     "traceback": [
      "\u001b[1;36m  Cell \u001b[1;32mIn[3], line 7\u001b[1;36m\u001b[0m\n\u001b[1;33m    x = yield\u001b[0m\n\u001b[1;37m        ^\u001b[0m\n\u001b[1;31mSyntaxError\u001b[0m\u001b[1;31m:\u001b[0m 'yield' outside function\n"
     ]
    }
   ],
   "source": [
    "\"\"\" yield 表达式\n",
    "PEP 342 -- Coroutines via Enhanced Generators把 yield 关键字升级成了表达式。\n",
    "所谓表达式（Expression），意味着它可以被解析成一个值。\n",
    "同时，yield语句的特性依然保留，不能出现在顶层代码中，必须在函数体内使用。\n",
    "\"\"\"\n",
    "# 赋值给变量\n",
    "x = yield\n",
    "\n",
    "# 计算后赋值给变量\n",
    "y = yield + 1\n",
    "\n",
    "# yield 可以作为函数入参，但是需要使用()括起来\n",
    "print((yield))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "x is None\n"
     ]
    },
    {
     "ename": "StopIteration",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mStopIteration\u001b[0m                             Traceback (most recent call last)",
      "Cell \u001b[1;32mIn[4], line 8\u001b[0m\n\u001b[0;32m      5\u001b[0m g \u001b[38;5;241m=\u001b[39m show_yield_value()\n\u001b[0;32m      6\u001b[0m \u001b[38;5;28mnext\u001b[39m(g)  \u001b[38;5;66;03m# 第1次\u001b[39;00m\n\u001b[1;32m----> 8\u001b[0m \u001b[38;5;28;43mnext\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mg\u001b[49m\u001b[43m)\u001b[49m  \u001b[38;5;66;03m# 第2次\u001b[39;00m\n\u001b[0;32m     10\u001b[0m \u001b[38;5;66;03m# 使用 next() 函数来驱动生成器的时候，yield 表达式的值总是为 None。\u001b[39;00m\n",
      "\u001b[1;31mStopIteration\u001b[0m: "
     ]
    }
   ],
   "source": [
    "def show_yield_value():\n",
    "    x = yield\n",
    "    print(f'x is {x}')\n",
    "\n",
    "g = show_yield_value()\n",
    "next(g)  # 第1次\n",
    "\n",
    "next(g)  # 第2次\n",
    "\n",
    "# 使用 next() 函数来驱动生成器的时候，yield 表达式的值总是为 None。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "can't send non-None value to a just-started generator",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mTypeError\u001b[0m                                 Traceback (most recent call last)",
      "Cell \u001b[1;32mIn[5], line 11\u001b[0m\n\u001b[0;32m      8\u001b[0m     \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mx is \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mx\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m'\u001b[39m)\n\u001b[0;32m     10\u001b[0m g \u001b[38;5;241m=\u001b[39m show_yield_value()\n\u001b[1;32m---> 11\u001b[0m \u001b[43mg\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msend\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43mhello\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m)\u001b[49m  \u001b[38;5;66;03m# 第 1 次只能是 None\u001b[39;00m\n",
      "\u001b[1;31mTypeError\u001b[0m: can't send non-None value to a just-started generator"
     ]
    }
   ],
   "source": [
    "\"\"\"\n",
    "为生成器增加一个 send() 方法，该方法可以接受一个入参。\n",
    "send 方法顾名思义，将该参数发送给生成器，使生成器恢复运行的同时，将该入参作为 yield表达式的值。\n",
    "\"\"\"\n",
    "def show_yield_value():\n",
    "    print('开始')\n",
    "    x = yield\n",
    "    print(f'x is {x}')\n",
    "\n",
    "g = show_yield_value()\n",
    "g.send('hello')  # 第 1 次只能是 None"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "开始\n"
     ]
    }
   ],
   "source": [
    "\"\"\"\n",
    "prime\n",
    "对于刚创建好的生成器，总是需要在第一次的时候 send(None) 值，使其运行到 yield 的地方暂停，这个步骤术语称为 prime。\n",
    "\"\"\"\n",
    "\n",
    "g.send(None)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "x is hello\n"
     ]
    },
    {
     "ename": "StopIteration",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mStopIteration\u001b[0m                             Traceback (most recent call last)",
      "Cell \u001b[1;32mIn[7], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m \u001b[43mg\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msend\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43mhello\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m)\u001b[49m  \u001b[38;5;66;03m# 第 2 次将 'hello' 作为 yield 表达式的值\u001b[39;00m\n",
      "\u001b[1;31mStopIteration\u001b[0m: "
     ]
    }
   ],
   "source": [
    "g.send('hello')  # 第 2 次将 'hello' 作为 yield 表达式的值"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### yield 表达式的优先级"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def add_yield_value():\n",
    "    x = yield + 1  # bug! 等价于 x = (yield 1)\n",
    "    print(f'x is {x}')\n",
    "\n",
    "g = add_yield_value()\n",
    "g.send(None)  # prime"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "x is 3\n"
     ]
    },
    {
     "ename": "StopIteration",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mStopIteration\u001b[0m                             Traceback (most recent call last)",
      "Cell \u001b[1;32mIn[12], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m \u001b[43mg\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msend\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m3\u001b[39;49m\u001b[43m)\u001b[49m\n",
      "\u001b[1;31mStopIteration\u001b[0m: "
     ]
    }
   ],
   "source": [
    "g.send(3)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "x is 4\n"
     ]
    },
    {
     "ename": "StopIteration",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mStopIteration\u001b[0m                             Traceback (most recent call last)",
      "Cell \u001b[1;32mIn[13], line 8\u001b[0m\n\u001b[0;32m      5\u001b[0m g \u001b[38;5;241m=\u001b[39m add_yield_value()\n\u001b[0;32m      6\u001b[0m g\u001b[38;5;241m.\u001b[39msend(\u001b[38;5;28;01mNone\u001b[39;00m)  \u001b[38;5;66;03m# prime\u001b[39;00m\n\u001b[1;32m----> 8\u001b[0m \u001b[43mg\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msend\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m3\u001b[39;49m\u001b[43m)\u001b[49m\n",
      "\u001b[1;31mStopIteration\u001b[0m: "
     ]
    }
   ],
   "source": [
    "def add_yield_value():\n",
    "    x = (yield) + 1  \n",
    "    print(f'x is {x}')\n",
    "\n",
    "g = add_yield_value()\n",
    "g.send(None)  # prime\n",
    "\n",
    "g.send(3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### send() 用法总结\n",
    "- send 是生成器对象的方法\n",
    "- 对于生成器对象 g，next(g) 等价于 g.send(None)\n",
    "- 只有当生成器处在「暂停」状态时，才能传入非None的值\n",
    "- send 方法是为了协程而增加的API，所以：\n",
    "    - 如果将生成器视作协程，就应该只用 send 方法\n",
    "    - 如果视作迭代器，就仍用 next"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 一个极简的 echo"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "hello\n",
      "world\n",
      "42\n",
      "stop\n"
     ]
    }
   ],
   "source": [
    "def gen_echo():\n",
    "    while True:\n",
    "        print((yield))\n",
    "\n",
    "echo = gen_echo()\n",
    "echo.send(None)  # prime\n",
    "\n",
    "for action in [\"hello\", \"world\", 42]:\n",
    "    echo.send(action)\n",
    "\n",
    "\n",
    "echo.send(StopIteration('stop')) # StopIteration 作为普通值传入，不会终止生成器"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "ename": "StopIteration",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mStopIteration\u001b[0m                             Traceback (most recent call last)",
      "Cell \u001b[1;32mIn[15], line 9\u001b[0m\n\u001b[0;32m      1\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[0;32m      2\u001b[0m \u001b[38;5;124;03m使用 close() 结束生成器\u001b[39;00m\n\u001b[0;32m      3\u001b[0m \u001b[38;5;124;03m当生成器作为迭代器来用的时候，它的生命周期取决于有多少元素可以迭代。\u001b[39;00m\n\u001b[0;32m      4\u001b[0m \u001b[38;5;124;03m而当作协程来用的时候，通常可以视作是在执行一个任务，我们希望任务的终止能够变得可控。\u001b[39;00m\n\u001b[0;32m      5\u001b[0m \u001b[38;5;124;03m新增的 close 方法就是用来结束一个协程：\u001b[39;00m\n\u001b[0;32m      6\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[0;32m      7\u001b[0m echo\u001b[38;5;241m.\u001b[39mclose()\n\u001b[1;32m----> 9\u001b[0m \u001b[43mecho\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msend\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43mhi\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m)\u001b[49m\n",
      "\u001b[1;31mStopIteration\u001b[0m: "
     ]
    }
   ],
   "source": [
    "\"\"\"\n",
    "使用 close() 结束生成器\n",
    "当生成器作为迭代器来用的时候，它的生命周期取决于有多少元素可以迭代。\n",
    "而当作协程来用的时候，通常可以视作是在执行一个任务，新增的 close 方法就是用来结束一个协程。\n",
    "\"\"\"\n",
    "echo.close()\n",
    "\n",
    "echo.send('hi')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\"\"\"\n",
    "由于 echo 协程的内容非常简单，所以可以直接结束。\n",
    "如果协程的代码比较复杂，它可能需要在结束的时候做一些善后处理，比如释放资源等。\n",
    "类似于 StopIteration 的实现机制，结束协程也是靠异常来实现的：\n",
    "\"\"\"\n",
    "def gen_echo_v2():\n",
    "    while True:\n",
    "        try:\n",
    "            x = yield\n",
    "        except GeneratorExit:\n",
    "            print('exit. bye!')\n",
    "            return  # 处理完异常后，必须要跳出循环\n",
    "        else:\n",
    "            print(x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "exit. bye!\n"
     ]
    }
   ],
   "source": [
    "echo_v2 = gen_echo_v2()\n",
    "echo_v2.send(None)  # 需要激活才能捕获到异常\n",
    "echo_v2.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "exit. bye!\n"
     ]
    }
   ],
   "source": [
    "# 除了显式地调用 close 方法，如果生成器对象被垃圾回收，也会自动调用 close：\n",
    "echo_v2 = gen_echo_v2()\n",
    "echo_v2.send(None)  # 需要激活才能捕获到异常\n",
    "\n",
    "# del echo_v2\n",
    "\n",
    "echo_v2 = 123"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 使用 throw() 将异常抛给 yield"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def gen_echo_v3():\n",
    "    while True:\n",
    "        try:\n",
    "            x = yield\n",
    "        except GeneratorExit:\n",
    "            print('exit. bye!')\n",
    "            return\n",
    "        except KeyboardInterrupt:\n",
    "            print('Ctrl-C ')\n",
    "        else:\n",
    "            print(x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "echo_v3 = gen_echo_v3()\n",
    "echo_v3.send(None) # prime "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Ctrl-C \n"
     ]
    }
   ],
   "source": [
    "echo_v3.throw(KeyboardInterrupt)  # 抛出异常"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "base",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
